library ieee;
use ieee.std_logic_1164.all;
use IEEE.NUMERIC_STD.all;
use ieee.std_logic_unsigned.all;

entity dcache_ctrl is
  port
    (
      clk, nReset                : in  std_logic;
      -- CPU side
      halt                       : in  std_logic;
      halt_out                   : out std_logic;
      dmemREN, dmemWEN           : in  std_logic;
      dmemAddr                   : in  std_logic_vector(31 downto 0);
      dmemWriteData              : in  std_logic_vector(31 downto 0);
      dmemReadData               : out std_logic_vector(31 downto 0);
      dmemWait                   : out std_logic;
      -- Arbiter side
      arbREN                     : out std_logic;  -- dcache read request
      arbWEN                     : out std_logic;  -- dcache write request
      arbWait                    : in  std_logic;  -- dcache must wait if 1
      arbAddr                    : out std_logic_vector(31 downto 0);  -- dcache address being read
      arbReadData                : in  std_logic_vector(31 downto 0);  -- dcache data to be read
      arbWriteData               : out std_logic_vector(31 downto 0);  -- dcache data to be written
      -- dcache side (sets 1 and 2)
      s1_tag, s2_tag             : out std_logic_vector(24 downto 0);
      s1_tagOut, s2_tagOut       : in  std_logic_vector(24 downto 0);
      s1_index, s2_index         : out std_logic_vector(3 downto 0);
      s1_wdat, s2_wdat           : out std_logic_vector(63 downto 0);  -- write data to be written on datWEN
      s1_wDirty, s2_wDirty       : out std_logic;  -- dirty bit to be written on datWEN
      s1_datWEN, s2_datWEN       : out std_logic;
      s1_rdat, s2_rdat           : in  std_logic_vector(63 downto 0);
      s1_rHit, s2_rHit           : in  std_logic;
      s1_rDirty, s2_rDirty       : in  std_logic;
      s1_rRecent, s2_rRecent     : in  std_logic;
      s1_wRecent, s2_wRecent     : out std_logic;
      s1_recentWEN, s2_recentWEN : out std_logic
      );
end dcache_ctrl;

architecture dcache_ctrl_arch of dcache_ctrl is

  type   state_type is(idle, s1_wb1, s1_wb2, s1_read1, s1_read2, s2_wb1, s2_wb2, s2_read1, s2_read2, halt_halted, halt_check1, halt_check2, halt_inc, halt_s1wb1, halt_s1wb2, halt_s2wb1, halt_s2wb2);
  signal currState, nextState : state_type;
  signal t_tag                : std_logic_vector(24 downto 0);
  signal t_index              : std_logic_vector(3 downto 0);
  signal halt_index           : std_logic_vector(3 downto 0);
  signal halt_indexNext       : std_logic_vector(3 downto 0);
  signal t_wordSelect         : std_logic;
  signal t_chosenSet          : std_logic;  -- 0=set1, 1=set2
  signal t_chosenData         : std_logic_vector(63 downto 0);
  signal t_chosenWord         : std_logic_vector(31 downto 0);
  signal t_chosenHit          : std_logic;

  signal t_readData, t_readDataNext : std_logic_vector(31 downto 0);

  signal s1_priority, s2_priority : std_logic_vector(1 downto 0);
begin

  t_tag        <= dmemAddr(31 downto 7);
  t_index      <= dmemAddr(6 downto 3);
  t_wordSelect <= dmemAddr(2);
  s1_tag       <= t_tag;
  s2_tag       <= t_tag;


  -- selects the data from either set
  chooseS1S2 : process(s1_rHit, s2_rHit, s1_rRecent, s2_rRecent)
  begin
    t_chosenSet <= '0';
    if(s1_rHit = '1' and s2_rHit = '0') then
      t_chosenSet <= '0';
    elsif(s1_rHit = '0' and s2_rHit = '1') then
      t_chosenSet <= '1';
    elsif(s1_rHit = '1' and s2_rHit = '1') then
      if(s1_rRecent = '1') then
        t_chosenSet <= '0';
      elsif(s2_rRecent = '1') then
        t_chosenSet <= '1';
      end if;
    else
      t_chosenSet <= '0';
    end if;
  end process;

  with t_chosenSet select t_chosenData <= s2_rdat when '1',
                                          s1_rdat when others;

  with t_chosenSet select t_chosenHit <= s2_rHit when '1',
                                         s1_rHit when others;

  -- select word from the chosen data path
  with t_wordSelect select t_chosenWord <= t_chosenData(63 downto 32) when '0',
                                           t_chosenData(31 downto 0) when others;

  -- send chosen word to output
  dmemReadData <= t_chosenWord;

  dcache_state : process(clk, nReset)
  begin
    if nReset = '0' then
      currState  <= idle;
      t_readData <= x"00000000";
      halt_index <= (others => '0');
    elsif rising_edge(clk) then
      currState  <= nextState;
      halt_index <= halt_indexNext;
      t_readData <= t_readDataNext;
    end if;
  end process dcache_state;


  nexttstate : process(dmemREN, dmemWEN, dmemAddr, dmemWriteData, s1_rdat, s2_rdat, s1_rHit, s2_rHit, s1_rDirty, s2_rDirty, s1_rRecent, s2_rRecent, arbWait, arbReadData, halt, halt_index, currState, t_chosenHit, s1_priority, s2_priority, t_readDataNext)
  begin
    nextState      <= currState;
    s1_priority    <= s1_rDirty & s1_rRecent;
    s2_priority    <= s2_rDirty & s2_rRecent;
    t_readDataNext <= t_readDataNext;
    halt_indexNext <= halt_index;
    case currState is
      
      when idle =>
        if(halt = '1') then
          nextState <= halt_check1;
        else
        
          if( t_chosenHit = '0' and ( dmemREN = '1' or dmemWEN = '1' ) ) then
            -- if miss
            -- evict something
            if(to_integer(unsigned(s1_priority)) <= to_integer(unsigned(s2_priority))) then
              -- choose s1
              if(s1_priority(1) = '1') then
                -- if dirty
                nextState <= s1_wb1;
              else
                nextState <= s1_read1;
              end if;
            else
              -- choose s2
              if(s2_priority(1) = '1') then
                -- if dirty
                nextState <= s2_wb1;
              else
                nextState <= s2_read1;
              end if;
            end if;
            
          end if;
          
        end if;
        
      when s1_wb1 =>
        if(arbWait = '0') then
          nextState <= s1_wb2;
        else
          nextState <= s1_wb1;
        end if;
        
      when s1_wb2 =>
        if(arbWait = '0') then
          nextState <= s1_read1;
        else
          nextState <= s1_wb2;
        end if;
        
      when s1_read1 =>
        if(arbWait = '0') then
          nextState      <= s1_read2;
          t_readDataNext <= arbReadData;
        else
          nextState <= s1_read1;
        end if;
        
      when s1_read2 =>
        if(arbWait = '0') then
          nextState <= idle;
        else
          nextState <= s1_read2;
        end if;
        
      when s2_wb1 =>
        if(arbWait = '0') then
          nextState <= s2_wb2;
        else
          nextState <= s2_wb1;
        end if;
        
      when s2_wb2 =>
        if(arbWait = '0') then
          nextState <= s2_read1;
        else
          nextState <= s2_wb2;
        end if;
        
      when s2_read1 =>
        if(arbWait = '0') then
          nextState      <= s2_read2;
          t_readDataNext <= arbReadData;
        else
          nextState <= s2_read1;
        end if;
        
      when s2_read2 =>
        if(arbWait = '0') then
          nextState <= idle;
        else
          nextState <= s2_read2;
        end if;

      when halt_s1wb1 =>
        if(arbWait = '0') then
          nextState <= halt_s1wb2;
        else
          nextState <= halt_s1wb1;
        end if;
      when halt_s1wb2 =>
        if(arbWait = '0') then
          nextState <= halt_check2;
        else
          nextState <= halt_s1wb2;
        end if;
      when halt_s2wb1 =>
        if(arbWait = '0') then
          nextState <= halt_s2wb2;
        else
          nextState <= halt_s2wb1;
        end if;
      when halt_s2wb2 =>
        if(arbWait = '0') then
          nextState <= halt_inc;
        else
          nextState <= halt_s2wb2;
        end if;
      when halt_inc =>
        if(halt_index = "1111") then
          nextState <= halt_halted;
        else
          nextState      <= halt_check1;
          halt_indexNext <= halt_index + '1';
        end if;
      when halt_check1 =>
        if(s1_priority(1) = '1') then
          -- if dirty
          nextState <= halt_s1wb1;
        else
          nextState <= halt_check2;
        end if;
      when halt_check2 =>
        if(s2_priority(1) = '1') then
           -- if dirty
          nextState <= halt_s2wb1;
        else
          nextState <= halt_inc;
        end if;
      when halt_halted =>
        nextState <= halt_halted;
        
      when others =>
        nextState <= idle;
        
    end case;
  end process nexttstate;


  outp : process (currState, dmemREN, dmemWEN, dmemAddr, dmemWriteData, s1_rdat, s2_rdat, s1_rHit, s2_rHit, s1_rDirty, s2_rDirty, s1_rRecent, s2_rRecent, s1_tagOut, s2_tagOut, t_tag, t_index, halt_index, arbWait, arbReadData, t_chosenHit, t_wordSelect, t_readData)
  begin
    s1_index     <= t_index;
    s2_index     <= t_index;
    -- to CPU
    --dmemReadData <= x"00000000";
    dmemWait     <= '0';
    -- to Arbiter
    arbREN       <= '0';
    arbWEN       <= '0';
    arbAddr      <= x"00000000";
    arbWriteData <= x"00000000";
    -- to cache sets
    s1_wdat      <= x"0000000000000000";
    s2_wdat      <= x"0000000000000000";
    s1_wDirty    <= '0';
    s2_wDirty    <= '0';
    s1_datWEN    <= '0';
    s2_datWEN    <= '0';
    s1_wRecent   <= '0';
    s2_wRecent   <= '0';
    s1_recentWEN <= '0';
    s2_recentWEN <= '0';
    halt_out     <= '0';
    case currState is
      
      when idle =>
        dmemWait     <= ((dmemWEN or dmemREN) and not t_chosenHit) or arbWait;
        s2_recentWEN <= dmemWEN or dmemREN;
        s1_recentWEN <= dmemWEN or dmemREN;
        if(t_chosenSet = '1') then
          s2_wDirty  <= dmemWEN and t_chosenHit;
          s2_datWEN  <= dmemWEN and t_chosenHit;
          s2_wRecent <= '1';
          s1_wRecent <= '0';
          if(t_wordSelect = '1') then
            s2_wdat <= s2_rdat(63 downto 32) & dmemWriteData;
          else
            s2_wdat <= dmemWriteData & s2_rdat(31 downto 0);
          end if;
        else
          s1_wDirty  <= dmemWEN and t_chosenHit;
          s1_datWEN  <= dmemWEN and t_chosenHit;
          s1_wRecent <= '1';
          s2_wRecent <= '0';
          if(t_wordSelect = '1') then
            s1_wdat <= s1_rdat(63 downto 32) & dmemWriteData;
          else
            s1_wdat <= dmemWriteData & s1_rdat(31 downto 0);
          end if;
        end if;
        
        
      when s1_wb1 =>
        -- write back ub
        dmemWait     <= '1';
        arbREN       <= '0';
        arbWEN       <= '1';
        arbAddr      <= s1_tagOut & t_index & "000";
        arbWriteData <= s1_rdat(63 downto 32);
        
      when s1_wb2 =>
                                        -- write back lb
        dmemWait     <= '1';
        arbREN       <= '0';
        arbWEN       <= '1';
        arbAddr      <= s1_tagOut & t_index & "100";
        arbWriteData <= s1_rdat(31 downto 0);
        
      when s1_read1 =>
        -- read ub
        dmemWait <= '1';
        arbREN   <= '1';
        arbWEN   <= '0';
        arbAddr  <= t_tag & t_index & "000";
        
      when s1_read2 =>
                                        -- read lb
        dmemWait  <= '1';
        arbREN    <= '1';
        arbWEN    <= '0';
        arbAddr   <= t_tag & t_index & "100";
        s1_wdat   <= t_readData & arbReadData;
        s1_wDirty <= '0';
        s1_datWEN <= not arbWait;
        
      when s2_wb1 =>
        -- write back ub
        dmemWait     <= '1';
        arbREN       <= '0';
        arbWEN       <= '1';
        arbAddr      <= s2_tagOut & t_index & "000";
        arbWriteData <= s2_rdat(63 downto 32);
        
      when s2_wb2 =>
                                        -- write back lb
        dmemWait     <= '1';
        arbREN       <= '0';
        arbWEN       <= '1';
        arbAddr      <= s2_tagOut & t_index & "100";
        arbWriteData <= s2_rdat(31 downto 0);
        
      when s2_read1 =>
        dmemWait <= '1';
        arbREN   <= '1';
        arbWEN   <= '0';
        arbAddr  <= t_tag & t_index & "000";
        
      when s2_read2 =>
        dmemWait  <= '1';
        arbREN    <= '1';
        arbWEN    <= '0';
        arbAddr   <= t_tag & t_index & "100";
        s2_wdat   <= t_readData & arbReadData;
        s2_wDirty <= '0';
        s2_datWEN <= not arbWait;

      when halt_halted =>
        halt_out <= '1';

      when halt_check1 =>
        s1_index <= halt_index;
      when halt_check2 =>
        s2_index <= halt_index;
      when halt_s1wb1 =>
        -- write back ub
        dmemWait     <= '1';
        arbREN       <= '0';
        arbWEN       <= '1';
        arbAddr      <= s1_tagOut & halt_index & "000";
        arbWriteData <= s1_rdat(63 downto 32);
        s1_index <= halt_index;
        
      when halt_s1wb2 =>
                                        -- write back lb
        dmemWait     <= '1';
        arbREN       <= '0';
        arbWEN       <= '1';
        arbAddr      <= s1_tagOut & halt_index & "100";
        arbWriteData <= s1_rdat(31 downto 0);
        s1_index <= halt_index;

      when halt_s2wb1 =>
        -- write back ub
        dmemWait     <= '1';
        arbREN       <= '0';
        arbWEN       <= '1';
        arbAddr      <= s2_tagOut & halt_index & "000";
        arbWriteData <= s2_rdat(63 downto 32);
        s2_index <= halt_index;
        
      when halt_s2wb2 =>
                                        -- write back lb
        dmemWait     <= '1';
        arbREN       <= '0';
        arbWEN       <= '1';
        arbAddr      <= s2_tagOut & halt_index & "100";
        arbWriteData <= s2_rdat(31 downto 0);
        s2_index <= halt_index;

      when others =>
        dmemWait <= ((dmemWEN or dmemREN) and not t_chosenHit) or arbWait;
        
    end case;
  end process;
  
  
end dcache_ctrl_arch;
